#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include "wrap.h"

#define MAX_LINE 80
#define SERVER_PORT 6666
#define INET_ADDRSTRLEN 16

int main(int argc, char *argv[]) {
    int i, max_i, max_fd, listen_fd, conn_fd,sock_fd;
    int n_ready,client[FD_SETSIZE]; 	/* FD_SETSIZE 默认为 1024 这是操作系统自定义宏，描述最大文件描述符个数*/
    // client 数组是用来模拟那个文件描述符数组，防止每次都要去遍历操作系统的位图
    ssize_t n;
    fd_set rset;
    fd_set all_set;
    char buf[MAX_LINE];
    char str[INET_ADDRSTRLEN]; 			/*  */
    socklen_t cliaddr_len;
    struct sockaddr_in cliaddr, serv_addr;

    listenfd = Socket(AF_INET, SOCK_STREAM, 0);
    bzero(&serv_addr, sizeof(serv_addr));  // 将serv_addr全部置零
    serv_addr.sin_family = AF_INET;
    serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
    serv_addr.sin_port = htons(SERV_PORT);

    bind(listenfd, (struct sockaddr *)&serv_addr, sizeof(serv_addr));

    listen(listenfd, 20); 		/* 默认最大128 */

    maxfd = listenfd; 			/* 初始化 */
    maxi = -1;					/* client[]的下标 */

    /* 初始化client数组，全部置零  */
    for (i = 0; i < FD_SETSIZE; i++)
    	client[i] = -1; 		/* 用-1初始化client[] */

    FD_ZERO(&all_set);
    FD_SET(listenfd, &all_set); /* 构造select监控文件描述符集 */
    for ( ; ; ) {
    	rset = all_set; 			/* 每次循环时都从新设置select监控信号集 */
    	nready = select(maxfd+1, &rset, NULL, NULL, NULL); // 只监听读事件， 写事件和异常不关心，事件也不用关系  =>> 返回满足的个数

    	if (nready < 0)
    		perr_exit("select error");
    	if (FD_ISSET(listenfd, &rset)) { /* 有新的连接请求 判断监听文件描述符是否在读事件集合里 */
    		cliaddr_len = sizeof(cliaddr);
    		connfd = Accept(listenfd, (struct sockaddr *)&cliaddr, &cliaddr_len); // 此时accept函数不会阻塞
    		printf("received from %s at PORT %d\n",
    				inet_ntop(AF_INET, &cliaddr.sin_addr, str, sizeof(str)),
    				ntohs(cliaddr.sin_port));
    		// 将当前文件描述符加入到自定义的client位图中，避免每次都去遍历
    		for (i = 0; i < FD_SETSIZE; i++) {
    			if (client[i] < 0) {
    				client[i] = connfd; /* 保存accept返回的文件描述符到client[]里 */
    				break;
    			}
    		}
    		/* 达到select能监控的文件个数上限 1024 */
    		if (i == FD_SETSIZE) {
    			fputs("too many clients\n", stderr);
    			exit(1);
    		}

    		FD_SET(connfd, &all_set); 	/* 添加一个新的文件描述符到监控信号集里 */
    		if (connfd > maxfd)
    			maxfd = connfd; 		/* select第一个参数需要 */
    		if (i > maxi)
    			maxi = i; 				/* 更新client[]最大下标值 */

    		if (--nready == 0)
    			continue; 				/* 如果没有更多的就绪文件描述符继续回到上面select阻塞监听,
    										负责处理未处理完的就绪文件描述符 */
    		}
    		for (i = 0; i <= maxi; i++) { 	/* 检测哪个clients 有数据就绪 */
    			if ( (sockfd = client[i]) < 0)
    				continue;
    			if (FD_ISSET(sockfd, &rset)) {
    				if ( (n = Read(sockfd, buf, MAXLINE)) == 0) {
    					Close(sockfd);		/* 当client关闭链接时，服务器端也关闭对应链接 */
    					FD_CLR(sockfd, &all_set); /* 解除select监控此文件描述符 */
    					client[i] = -1;
    				} else {
    					int j;
    					for (j = 0; j < n; j++)
    						buf[j] = toupper(buf[j]);
    					Write(sockfd, buf, n);
    				}
    				if (--nready == 0)
    					break;
    			}
    		}
    	}
    	close(listenfd);
    	return 0;
}